package com.hup.minicontroller.common.hardware;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
import android.annotation.SuppressLint;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.graphics.Path;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import com.hup.minicontroller.common.framework.AppLogger;
import com.hup.minicontroller.common.util.Supplier;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Setter;

/**
 * 控制设备的工具类,实现功能:触摸事件,按下按钮事件<pre>
 * .部分方法在高版本系统中已不适用!!如打开[最近应用页面]
 * .需要配置成[系统app],如果无权限,则能在本app界面内操作,app外的操作将会报错(被捕获)
 * .AndroidManifest
 *      android:sharedUserId="android.uid.system"
 * .gradle
 *      android {
 *          signingConfigs {
 *              debug {
 *                  storeFile file('xxxx/xxxx.keystore')
 *              }
 *          }
 *
 * 使用 {@link AccessibilityService}:
 * .在非系统应用时, 依靠无障碍服务实现全局触摸和按键事件
 * .触摸事件是畸形的!!! 尤其是拖拽事件,暂无完善的办法; 无障碍的模拟触摸功能,本是'模拟完整的触摸操作',不支持'实时指定按下拖拽抬起'
 * .使用无障碍服务, (每次启动时)需要用户开启app的无障碍服务
 */
public class ActionMocker {
    private static final String TAG = ActionMocker.class.getSimpleName();

    private final Instrumentation instrumentation;
    private final Context context;
    private final Handler subHandler;
    private final MouseParam mouseParam;
    /**
     * notNull-使用无障碍服务实现功能
     */
    @Setter
    private Supplier<AccessibilityService> accessibilityServiceSupplier;

    /**
     * @param subHandler 子线程Handler,某些操作需要用子线程执行
     */
    public ActionMocker(Context context, Handler subHandler) {
        instrumentation = new Instrumentation();
        this.context = context;
        this.subHandler = subHandler;
        mouseParam = new MouseParam();
    }

    private boolean canAccessibility() {
        if (accessibilityServiceSupplier == null) return false;
        AccessibilityService accessibilityService = accessibilityServiceSupplier.get();
        if (accessibilityService == null) return false;
        return VERSION.SDK_INT >= VERSION_CODES.O;
    }

    private void postAction(String desc, Callable<Boolean> action) {
        subHandler.post(() -> {
            try {
                Boolean result = action.call();
                if (!result) Log.e(TAG, desc + ": 执行失败?");
            } catch (Exception e) {
                Log.e(TAG, AppLogger.getStack(desc + "错误:", e));
            }
        });
    }

    @SuppressLint("NewApi")
    public void click(int x, int y) {
        postAction("click", () -> {
            if (canAccessibility()) {
                AccessibilityService service = accessibilityServiceSupplier.get();
                Path path = new Path();
                path.moveTo(x, y);
                GestureDescription gesture = new GestureDescription.Builder()
                        .addStroke(new StrokeDescription(path, 0, 20, false))
                        .build();
                return service.dispatchGesture(gesture, null, null);
            }

            //
            instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                    MotionEvent.ACTION_DOWN, x, y, 0));
            Thread.sleep(20);
            instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                    MotionEvent.ACTION_UP, x, y, 0));
            return true;
        });
    }

    @SuppressLint("NewApi")
    public void press(int x, int y) {
        postAction("press", () -> {
            //Log.i(TAG, "press: [" + x + "," + y + "]");
            mouseParam.onPress(x, y);

            if (canAccessibility()) {
                //[按下]效果
                AccessibilityService service = accessibilityServiceSupplier.get();
                Path path = new Path();
                path.moveTo(x, y);
                GestureDescription gesture = new GestureDescription.Builder()
                        .addStroke(new StrokeDescription(path, 0, 1, true))//willContinue=true,动作完成后'不抬起手指'
                        .build();
                return service.dispatchGesture(gesture, null, null);
            }

            //
            instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                    MotionEvent.ACTION_DOWN, x, y, 0));
            return true;
        });
    }

    @SuppressLint("NewApi")
    public void up(int x, int y) {
        postAction("up", () -> {
            //Log.i(TAG, "up: [" + x + "," + y + "], mouseParam=" + mouseParam);

            if (canAccessibility()) {
                AccessibilityService service = accessibilityServiceSupplier.get();
                /*
                gesture说明
                [点击]效果
                    .一次[path.moveTo + addStroke]就是点击效果
                [按住]效果
                    .一次[path.moveTo + addStroke]
                    .且StrokeDescription.willContinue=true,才会按住不放
                    .要[抬起]时, 需要path.moveTo(100000, 100000)到屏幕外, 后addStroke; 否则会在屏幕内[多触发一次点击]
                [短按下+按路径拖拽]效果
                    .多次调用 path.lineTo 设置路径
                    .Builder.addStroke 设置动作时长
                    .问题:不能控制每个节点的停留时长
                [长按+拖拽]效果
                    .无法实现
                API说明
                    .多次[addStroke]实际是[多指操作]
                    .多次[dispatchGesture]实际是[多次'按下+抬起'操作]
                    .所以不能实现 [长按后,停顿,然后拖拽]
                 */
                GestureDescription gesture;
                if (mouseParam.movePoints.isEmpty()) {
                    //[点击,长按]中途没移动: 直接抬起
                    Path path = new Path();
                    if (System.currentTimeMillis()- mouseParam.pressTime < 100) {
                        //点击: 直接抬起
                        path.moveTo(x, y);
                    } else {
                        //长按: 在屏幕外抬起, 否则会[多触发一次点击]
                        path.moveTo(100000, 100000);
                    }
                    gesture = new GestureDescription.Builder()
                            .addStroke(new StrokeDescription(path, 0, 1))
                            .build();
                } else {
                    //移动过
                    gesture = mouseParam.getDragGesture(x, y);
                }
                return service.dispatchGesture(gesture, null, null);
            }

            //
            instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                    MotionEvent.ACTION_UP, x, y, 0));
            return true;
        });
    }

    @SuppressLint("NewApi")
    public void move(int x, int y) {
        postAction("move", () -> {
            //Log.i(TAG, "move: [" + x + "," + y + "]");
            mouseParam.onMove(x, y);

            if (canAccessibility()) {
                AccessibilityService service = accessibilityServiceSupplier.get();
                /*
                拖拽操作: 马上取消[按下]状态
                在屏幕外触发一次事件,实现[抬起]效果;
                 */
                Path path = new Path();
                path.moveTo(100000, 100000);
                GestureDescription gesture = new GestureDescription.Builder()
                        .addStroke(new StrokeDescription(path, 0, 1, false))
                        .build();
                return service.dispatchGesture(gesture, null, null);
            }

            //
            instrumentation.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
                    MotionEvent.ACTION_MOVE, x, y, 0));
            return true;
        });
    }

    public void buttonBack() {
        postAction("buttonBack", () -> {
            if (canAccessibility()) {
                AccessibilityService service = accessibilityServiceSupplier.get();
                return service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
            }

            //
            instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
            return true;
        });
    }

    public void buttonHome() {
        postAction("buttonHome", () -> {
            if (canAccessibility()) {
                AccessibilityService service = accessibilityServiceSupplier.get();
                return service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
            }

            //
            Intent homeIntent = new Intent(Intent.ACTION_MAIN);
            homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            homeIntent.addCategory(Intent.CATEGORY_HOME);
            context.startActivity(homeIntent);
            if (Build.VERSION.SDK_INT > VERSION_CODES.M) {
                Log.w(TAG, "buttonHome: 非当前app时,系统版本未支持操作");
                return false;
            }
            return true;
        });
    }

    @SuppressWarnings({"ConstantConditions", "RedundantArrayCreation"})
    public void buttonApp() {
        postAction("buttonApp", () -> {
            if (canAccessibility()) {
                AccessibilityService service = accessibilityServiceSupplier.get();
                return service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
            }

            //
            if (Build.VERSION.SDK_INT < VERSION_CODES.M) {
                //版本号是乱写的,安卓7.1的系统异常:找不到方法com.android.internal.statusbar.IStatusBarService.toggleRecentApps
                @SuppressLint("PrivateApi") Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
                Method getService = serviceManagerClass.getMethod("getService", String.class);
                IBinder iBinder = (IBinder) getService.invoke(serviceManagerClass, "statusbar");
                Class<?> statusBarClass = Class.forName(iBinder.getInterfaceDescriptor());
                Method clearAll = statusBarClass.getMethod("toggleRecentApps");
                clearAll.setAccessible(true);
                Object statusBarObject = statusBarClass.getClasses()[0].getMethod("asInterface", IBinder.class)
                        .invoke(null, new Object[]{iBinder});
                clearAll.invoke(statusBarObject);
                return true;
            } else {
//                ComponentName componentName = new ComponentName("com.android.systemui",
//                        "com.android.systemui.recents.RecentsActivity");
//                Intent intent = new Intent();
//                intent.setComponent(componentName);
//                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
//                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
//                context.startActivity(intent);
                /*
                不能用了???
                https://blog.csdn.net/hacker_crazy/article/details/78399303
                 */
                Log.e(TAG, "buttonApp: 系统版本未支持操作");
                return false;
            }
        });
    }

    @Data
    private static class MouseParam {
        private int x0 = 0;
        private int y0 = 0;
        private long pressTime = System.currentTimeMillis();
        private LinkedList<MovePoint> movePoints = new LinkedList<>();

        public void onPress(int x, int y) {
            x0 = x;
            y0 = y;
            pressTime = System.currentTimeMillis();
            movePoints.clear();
        }

        public void onMove(int x, int y) {
            long current = System.currentTimeMillis();
            if (movePoints.isEmpty()) {
                //首次移动,重置 pressTime[当作是拖拽 的开始时间]
                pressTime = current;

                movePoints.add(new MovePoint(current, x, y));
                //Log.i(TAG, "onMove: add");
                return;
            }

            MovePoint last = movePoints.getLast();
            if (current - last.time < 5) {
                //小于5ms就更新坐标: 可以处理元素过多,和 下文Stroke.duration过小的问题
                last.x = x;
                last.y = y;
                //Log.i(TAG, "onMove: update");
            } else {
                movePoints.add(new MovePoint(current, x, y));
                //Log.i(TAG, "onMove: add");
            }
        }

        /**
         * @param x [x,y]抬起事件的坐标
         */
        @RequiresApi(api = VERSION_CODES.O)
        public GestureDescription getDragGesture(int x, int y) {
            onMove(x, y);

            //限制Stroke长度, addStroke的最大长度=19; 还要减去[按下]的点
            List<MovePoint> points = fitListSize(movePoints, 19 - 1);
            //Log.i(TAG, "getDragGesture: size=" + movePoints.size() + "->" + points.size());

            GestureDescription.Builder builder = new GestureDescription.Builder();

            ////按下的点
            Path path = new Path();
            path.moveTo(x0, y0);
            for (MovePoint point : points) {
                path.lineTo(point.x, point.y);
            }
            builder.addStroke(new StrokeDescription(path, 0, System.currentTimeMillis() - pressTime));
            return builder.build();
        }


        /**
         * 限制list的最大长度;
         * 删除规则:
         * .按[平均步长]删除元素
         * .一定保留[头,尾]元素
         * <p>
         * 注意: 算法没完善, 可能出现 res.size<max的情况
         */
        public <E> List<E> fitListSize(List<E> list, int max) {
            if (list.size() <= max) return list;

            int size = list.size();
            max -= 1;
            int step = size / max;
            if (size % max != 0) {
                step += 1;
            }
            //log.info("sizeOld={}, max={}, step={}", size, max + 1, step);

            ArrayList<E> res = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                //log.info("i={}, val={}", i, i % step);
                if (i % step == 0 || i == size - 1) {
                    res.add(list.get(i));
                }
            }
            //log.info("res: size={}, {}", res.size(), res);
            return res;
        }


        @NonNull
        @Override
        public String toString() {
            return "MouseParam{" +
                    "x0=" + x0 +
                    ", y0=" + y0 +
                    ", pressTime=" + pressTime +
                    ", movePoints=" + movePoints.size() +
                    '}';
        }

        @AllArgsConstructor
        private static class MovePoint {
            /**
             * 事件发生的时间
             */
            private final long time;
            private int x;
            private int y;
        }
    }

}
